/**
 * @name Overwriting attribute in super-class or sub-class
 * @description Assignment to self attribute overwrites attribute previously defined in subclass or superclass __init__ method.
 * @kind problem
 * @tags reliability
 *       maintainability
 *       modularity
 * @problem.severity warning
 * @sub-severity low
 * @precision medium
 * @id py/overwritten-inherited-attribute
 */

import python

class InitCallStmt extends ExprStmt {
    InitCallStmt() {
        exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() |
            attr.getName() = "__init__"
        )
    }
}

predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) {
    write_attr.getScope() = subinit and
    self_write_stmt(write_attr, _) and
    exists(Stmt top | top.contains(write_attr) or top = write_attr |
        (
            exists(int i, int j, InitCallStmt call | call.getScope() = subinit |
                i > j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "superclass"
            )
            or
            exists(int i, int j, InitCallStmt call | call.getScope() = subinit |
                i < j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "subclass"
            )
        )
    )
}

predicate self_write_stmt(Stmt s, string attr) {
    exists(Attribute a, Name self |
        self = a.getObject() and
        s.contains(a) and
        self.getId() = "self" and
        a.getCtx() instanceof Store and
        a.getName() = attr
    )
}

predicate both_assign_attribute(Stmt s1, Stmt s2, Function f1, Function f2) {
    exists(string name |
        s1.getScope() = f1 and
        s2.getScope() = f2 and
        self_write_stmt(s1, name) and
        self_write_stmt(s2, name)
    )
}

predicate attribute_overwritten(
    AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname
) {
    exists(
        FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass,
        AssignStmt subattr, AssignStmt superattr
    |
        (
            classtype = "superclass" and
            classname = superclass.getName() and
            overwrites = subattr and
            overwritten = superattr
            or
            classtype = "subclass" and
            classname = subclass.getName() and
            overwrites = superattr and
            overwritten = subattr
        ) and
        /* OK if overwritten in subclass and is a class attribute */
        (not exists(superclass.declaredAttribute(name)) or classtype = "subclass") and
        superclass.declaredAttribute("__init__") = superinit and
        subclass.declaredAttribute("__init__") = subinit and
        superclass = subclass.getASuperType() and
        overwrites_which(subinit.getFunction(), subattr, classtype) and
        both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction()) and
        self_write_stmt(superattr, name)
    )
}

from string classtype, AssignStmt overwrites, AssignStmt overwritten, string name, string classname
where attribute_overwritten(overwrites, overwritten, name, classtype, classname)
select overwrites,
    "Assignment overwrites attribute " + name + ", which was previously defined in " + classtype +
        " $@.", overwritten, classname
